package cn.icanci.loopstack.rec.engine.script.impl;

import cn.hutool.http.Method;
import cn.hutool.json.JSONUtil;
import cn.icanci.loopstack.rec.engine.script.RecScriptEngine;
import cn.icanci.loopstack.rec.engine.script.client.Client;
import cn.icanci.loopstack.rec.engine.script.client.http.HttpClientImpl;
import cn.icanci.loopstack.rec.engine.script.context.RecScriptEngineContext;
import cn.icanci.loopstack.rec.engine.script.factory.ScriptEngineFactory;
import cn.icanci.loopstack.rec.engine.script.wrapper.HttpResponseWrapper;
import cn.icanci.loopstack.rec.common.enums.HttpRequestTypeEnum;
import cn.icanci.loopstack.rec.common.enums.ScriptTypeEnum;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.script.*;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;

/**
 * @author icanci
 * @since 1.0 Created in 2022/11/12 22:46
 */
public class RecScriptEngineImpl implements RecScriptEngine {

    private static final Logger logger = LoggerFactory.getLogger(RecScriptEngineImpl.class);

    /** http实例 */
    private static final Client CLIENT = HttpClientImpl.getInstance();

    @Override
    public ScriptEngine findEngine(ScriptTypeEnum scriptType) {
        return cn.icanci.loopstack.rec.engine.script.factory.ScriptEngineFactory.getScriptEngine(scriptType);
    }

    @Override
    public CompiledScript compile(ScriptTypeEnum scriptType, String scriptContent) {
        try {
            ScriptEngine scriptEngine = cn.icanci.loopstack.rec.engine.script.factory.ScriptEngineFactory.getScriptEngine(scriptType);
            if (scriptEngine instanceof Compilable) {
                Compilable compilable = (Compilable) scriptEngine;
                return compilable.compile(scriptContent);
            }
        } catch (Throwable t) {
            // No Op
            logger.error("[RecScriptEngine][compile] compile error:{}", t.getMessage());
        }
        return null;
    }

    /**
     * 执行脚本
     *
     * @param scriptType 脚本类型
     * @param script 脚本内容
     * @return 脚本执行返回上下文
     */
    @Override
    public RecScriptEngineContext<Object> eval(ScriptTypeEnum scriptType, String script) {
        return eval(scriptType, new SimpleBindings(), script);
    }

    /**
     * 执行脚本
     *
     * @param scriptType 脚本类型
     * @param bindings 脚本运行时参数
     * @param script 脚本内容
     * @return 脚本执行返回上下文
     */
    @Override
    public RecScriptEngineContext<Object> eval(ScriptTypeEnum scriptType, Bindings bindings, String script) {
        RecScriptEngineContext<Object> context = new RecScriptEngineContext<>();
        context.setScriptType(scriptType);
        context.setScriptContent(script);
        context.setType(Object.class);
        context.setBindings(bindings);
        try {
            ScriptEngine scriptEngine = cn.icanci.loopstack.rec.engine.script.factory.ScriptEngineFactory.getScriptEngine(scriptType);
            context.setScriptEngine(scriptEngine);
            if (scriptEngine instanceof Compilable) {
                Compilable compilable = (Compilable) scriptEngine;
                context.setCompilable(compilable);
                CompiledScript compile = compilable.compile(script);
                context.setCompiledScript(compile);
                Object eval = compile.eval(bindings);
                context.setRetVal(eval);
            } else {
                Object eval = scriptEngine.eval(script, bindings);
                context.setRetVal(eval);
            }
        } catch (Throwable t) {
            context.setThrowable(t);
        }
        return context;
    }

    /**
     * 执行脚本
     *
     * @param scriptType 脚本类型
     * @param script 脚本内容
     * @param clazz 脚本执行返回类型
     * @param <T> 泛型
     * @return 脚本执行返回上下文
     */
    @Override
    public <T> RecScriptEngineContext<T> eval(ScriptTypeEnum scriptType, String script, Class<T> clazz) {
        return eval(scriptType, new SimpleBindings(), script, clazz);
    }

    /**
     * 执行脚本
     *
     * @param scriptType 脚本类型
     * @param bindings 脚本运行时参数
     * @param script 脚本内容
     * @param clazz 脚本执行返回类型
     * @param <T> 泛型
     * @return 脚本执行返回上下文
     */
    @Override
    public <T> RecScriptEngineContext<T> eval(ScriptTypeEnum scriptType, Bindings bindings, String script, Class<T> clazz) {
        RecScriptEngineContext<T> context = new RecScriptEngineContext<>();
        context.setScriptType(scriptType);
        context.setScriptContent(script);
        context.setType(clazz);
        context.setBindings(bindings);
        try {
            ScriptEngine scriptEngine = ScriptEngineFactory.getScriptEngine(scriptType);
            context.setScriptEngine(scriptEngine);
            if (scriptEngine instanceof Compilable) {
                Compilable compilable = (Compilable) scriptEngine;
                context.setCompilable(compilable);
                CompiledScript compile = compilable.compile(script);
                context.setCompiledScript(compile);
                Object eval = compile.eval(bindings);
                context.setRetVal((T) eval);
            } else {
                Object eval = scriptEngine.eval(script, bindings);
                context.setRetVal((T) eval);
            }
        } catch (Throwable t) {
            context.setThrowable(t);
        }
        return context;
    }

    @Override
    public HttpResponseWrapper httpEval(HttpRequestTypeEnum requestType, String reqUrl, String reqParam, int timeout) {

        Method method = null;

        switch (requestType) {
            case GET:
                method = Method.GET;
                break;
            case POST:
                method = Method.POST;
                break;
            default:
                //  no op
        }

        if (method == null) {
            throw new NullPointerException("HttpMethod is Null !");
        }

        if (timeout <= 0) {
            throw new IllegalArgumentException("Http Request Timeout is Less than 0!");
        }

        HashMap<String, String> headers = Maps.newHashMap();
        Map reqMap = Maps.newHashMap();
        if (StringUtils.isNotBlank(reqParam)) {
            reqMap = JSONUtil.toBean(reqParam, Map.class);
        }
        Client.RpcRequest rpcRequest = new Client.RpcRequest(reqUrl, reqMap, headers, method, timeout, TimeUnit.SECONDS, 0);

        HttpResponseWrapper wrapper = new HttpResponseWrapper();
        try {
            wrapper.setResponse(CLIENT.call(rpcRequest, String.class));
        } catch (Throwable e) {
            wrapper.setException(e);
        }
        return wrapper;
    }

}
